﻿using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

public class FM_ScoreBase
{
    private const string TAG = Constants.TAG;

    public FM_TimeSignatureValue TimeSignature_n;    //numerator
    public FM_TimeSignatureValue TimeSignature_d;    //denominator
    public FM_KeySignatureValue KeySignature;
    public FM_StaveCount StaveCount;
    public int VoiceCount;

    public FM_KeySignature FirstStaveKey, SecondStaveKey;

    public FM_ClefValue FirstStaveClef { get; set; }
    public FM_ClefValue SecondStaveClef { get; set; }

    public FM_Score score;
    public readonly List<FM_BaseNote> StaveNotes = new List<FM_BaseNote>();
    public readonly List<FM_Tie> Ties = new List<FM_Tie>();
    public readonly List<FM_Tuplet> Tuplets = new List<FM_Tuplet>();
    public readonly List<FM_Beam> Beams = new List<FM_Beam>();

    public readonly Dictionary<string, List<FM_Note>> TieNotes = new Dictionary<string, List<FM_Note>>();
    public bool inTuplet = false;
    public int TupletPosition = 0;
    public int TupletSize = 0;
    public int currentTuplet = 0;
    public List<FM_BaseNote> TupletNotes;
    public bool inBeam = false;
    public int currentBeam = 0;
    public List<FM_BaseNote> BeamNotes;

    public FM_ScoreBase(FM_Score score)
    {
        this.score = score;
        StaveCount = FM_StaveCount._1;
        SetVoiceCount(1);
        SetFirstStaveClef(FM_ClefValue.TREBLE);
        SetSecondStaveClef(FM_ClefValue.BASS);
        SetTimeSignature(FM_TimeSignatureValue.None, FM_TimeSignatureValue.None);
        SetKeySignature(FM_KeySignatureValue.DO);
    }

    public FM_TimeSignatureValue GetTimeSignature_n()
    {
        return TimeSignature_n;
    }

    public FM_TimeSignatureValue GetTimeSignature_d()
    {
        return TimeSignature_d;
    }

    public void SetTimeSignature(FM_TimeSignatureValue numerator, FM_TimeSignatureValue denominator)
    {
        TimeSignature_n = numerator;
        TimeSignature_d = denominator;
    }

    public FM_StaveCount GetStaveCount()
    {
        return StaveCount;
    }

    public int GetVoiceCount()
    {
        return VoiceCount;
    }

    public void SetVoiceCount(int voiceCount)
    {
        VoiceCount = voiceCount;
    }

    public FM_ClefValue GetFirstStaveClef()
    {
        return FirstStaveClef;
    }

    public void SetFirstStaveClef(FM_ClefValue firstStaveClef)
    {
        FirstStaveClef = firstStaveClef;
        SetKeySignature(KeySignature);
    }

    public FM_ClefValue GetSecondStaveClef()
    {
        return SecondStaveClef;
    }

    public void SetSecondStaveClef(FM_ClefValue secondStaveClef)
    {
        SecondStaveClef = secondStaveClef;
        SetKeySignature(KeySignature);
    }

    public FM_KeySignatureValue GetKeySignature()
    {
        return KeySignature;
    }

    public void SetKeySignature(FM_KeySignatureValue keySignature)
    {
        KeySignature = keySignature;
        FirstStaveKey = new FM_KeySignature(FirstStaveClef, keySignature, this);
        SecondStaveKey = new FM_KeySignature(SecondStaveClef, keySignature, this);
    }

    public void ClearStaveNotes()
    {
        StaveNotes.Clear();
        Tuplets.Clear();
        Beams.Clear();
        Ties.Clear();
        StaveCount = FM_StaveCount._1;
        SetVoiceCount(1);
        SetFirstStaveClef(FM_ClefValue.TREBLE);
        SetSecondStaveClef(FM_ClefValue.BASS);
        SetTimeSignature(FM_TimeSignatureValue.None, FM_TimeSignatureValue.None);
        SetKeySignature(FM_KeySignatureValue.DO);
    }

    public void AddStaveNote(FM_BaseNote n)
    {
        AddStaveNote(n, 0);
    }

    public void AddStaveNote(FM_BaseNote n, int stave)
    {
        if (n is FM_BarNote) stave = 0;
        if (stave == 1) StaveCount = FM_StaveCount._2;
        n.stave = stave;
        StaveNotes.Add(n);
    }

    public void AddChord(List<FM_BaseNote> n, List<int> stave)
    {
        if (n[0] is FM_Clef)
        {
            n[0].stave = stave[0];
            StaveNotes.Add(n[0]);
        }
        else
        {
            FM_Chord C = new FM_Chord(this);
            for (int i = 0; i < n.Count; i++)
            {
                if (stave[i] == 1) StaveCount = FM_StaveCount._2;
                n[i].stave = stave[i];
                C.AddNote(n[i]);
            }
            C.Compute();
            StaveNotes.Add(C);
        }
    }

    public void AddToTie(string tie, FM_Note n)
    {
        if (TieNotes.ContainsKey(tie))
        {
            TieNotes[tie].Add(n);
            EndTie(tie);
        }
        else
        {
            List<FM_Note> noteList = new List<FM_Note> { n };
            TieNotes[tie] = noteList;
        }
    }

    protected void EndTie(string tie)
    {
        List<FM_Note> noteList = TieNotes[tie];
        if (noteList.Count != 2) return;
        if (noteList[0].stave != noteList[1].stave || noteList[0].octave != noteList[1].octave || !noteList[0].note.Equals(noteList[1].note))
            return;
        FM_Tie t;
        if (tie.ToLower().Contains("a")) t = new FM_Tie(score, true);
        else if (tie.ToLower().Contains("b")) t = new FM_Tie(score, false);
        else t = new FM_Tie(score);
        t.AddStart(noteList[0]);
        t.AddEnd(noteList[1]);
        noteList[0].isTieStart = true;
        noteList[1].isTieEnd = true;
        Ties.Add(t);
        TieNotes.Remove(tie);
    }

    public void BeginTuplet(string s)
    {
        inTuplet = true;
        TupletNotes = new List<FM_BaseNote>();
        TupletPosition = 1;
        TupletSize = 0;
        if (s.ToLower().Contains("a"))
        {
            TupletPosition = 0;
            try
            {
                string size = s.ToLower().Substring(s.ToLower().IndexOf("a") + 1);
                TupletSize = int.Parse(size);
            }
            catch (Exception e)
            {
                Debug.LogError(e.Message + "\n" + e.StackTrace);
            }
        }
        if (s.ToLower().Contains("b"))
        {
            TupletPosition = 1;
            try
            {
                string size = s.ToLower().Substring(s.ToLower().IndexOf("b") + 1);
                TupletSize = int.Parse(size);
            }
            catch (Exception e)
            {
                Debug.LogError(e.Message + "\n" + e.StackTrace);
            }
        }
    }

    public void AddToTuplet(FM_BaseNote n)
    {
        if (inTuplet) TupletNotes.Add(n);
    }

    public void EndTuplet()
    {
        inTuplet = false;
        for (int i = 0; i < TupletNotes.Count; i++)
            if (!(TupletNotes[i] is FM_Note || TupletNotes[i] is FM_Pause)) return;
        int stave = TupletNotes[0].stave;
        if (TupletSize == 0)
        {
            int minDuration = 6000;
            int maxDuration = 0;
            int allDuration = 0;
            TupletSize = TupletNotes.Count;
            for (int i = 0; i < TupletSize; i++)
            {
                int d = (int)(FM_Const.GetDurationMs(TupletNotes[i].duration) * 1000);
                allDuration += d;
                if (d < minDuration) minDuration = d;
                if (d > maxDuration) maxDuration = d;
            }
            if (allDuration % minDuration == 0) TupletSize = allDuration / minDuration;
            if (allDuration % maxDuration == 0) TupletSize = allDuration / maxDuration;
        }
        for (int i = 0; i < TupletNotes.Count; i++)
            if (TupletNotes[i].stave != stave) return;
        FM_Tuplet t = new FM_Tuplet(score, TupletSize, currentTuplet, TupletPosition);
        currentTuplet++;
        for (int i = 0; i < TupletNotes.Count; i++)
        {
            TupletNotes[i].tuplet = true;
            TupletNotes[i].tupletSize = TupletNotes.Count;
            t.AddNote(TupletNotes[i]);
        }
        Tuplets.Add(t);
    }

    public void BeginBeam()
    {
        inBeam = true;
        BeamNotes = new List<FM_BaseNote>();
    }

    public void AddToBeam(FM_Note n)
    {
        if (inBeam) BeamNotes.Add(n);
    }
    //FM_Key  String2Key(string str)
    //{
    //    FM_Key key = new FM_Key();
    //    return key;
    //}
    public void EndBeam()
    {
        inBeam = false;
        if (BeamNotes.Count < 2) return;
        for (int i = 0; i < BeamNotes.Count; i++)
            if (!(BeamNotes[i] is FM_Note)) return;


        for (int i = 0; i < BeamNotes.Count; i++)
        {
            var d = BeamNotes[i].duration;
            //Debug.LogError($"EndBeam  {i} d:{d}");
            if (d == FM_DurationValue.NOTE_WHOLE) return;
            if (d == FM_DurationValue.NOTE_WHOLE_D) return;
            if (d == FM_DurationValue.NOTE_HALF) return;
            if (d == FM_DurationValue.NOTE_HALF_D) return;
            if (d == FM_DurationValue.NOTE_QUARTER) return;
            if (d == FM_DurationValue.NOTE_QUARTER_D) return;
        }
        FM_Beam t = new FM_Beam(score, currentBeam);
        currentBeam++;
        for (int i = 0; i < BeamNotes.Count; i++)
        {
            ((FM_Note)BeamNotes[i]).beam = true;
            t.AddNote((FM_Note)BeamNotes[i]);
        }
        Beams.Add(t);
    }

    public int GetNoteCount()
    {
        return StaveNotes.Count;
    }

    public FM_BaseNote GetNote(int index)
    {
        if (index < 0 || index > StaveNotes.Count - 1)
            return null;
        return StaveNotes[index];
    }

    public FM_BaseNote GetLastNote()
    {
        if (GetNoteCount() == 0)
            return null;
        return StaveNotes[StaveNotes.Count - 1];
    }
    public int LoadFromJson(JObject obj)
    {
        List<string> keyList = new List<string>();
        List<string> clefList = new List<string>();
        string keySignature;
        string timeSignature;

        //string allKeys = "";
        try
        {
            JArray keys = obj["keys"] as JArray;
            keyList.AddRange(keys.Select(k => k.ToString()));
            //foreach (var key in keys)
            //    allKeys += key.ToString()+"\n";

            JArray clef = obj["clef"] as JArray;
            clefList.AddRange(clef.Select(c => c.ToString()));

            timeSignature = obj.Value<string>("timesignature") ?? "4/4";
            keySignature = obj.Value<string>("keysignature") ?? "DO";
        }
        catch (Exception e)
        {
            return -1;
        }
        //GameDebug.Log(allKeys);

        ClearStaveNotes();
        SetTimeSignature(FM_Const.GetTimeSignatureN(timeSignature), FM_Const.GetTimeSignatureD(timeSignature));
        SetKeySignature(FM_Const.StringToKeySignature(keySignature));

        var firstStaveClef = FM_ClefValue.TREBLE;
        var secondStaveClef = FM_ClefValue.BASS;

        if (clefList.Count >= 1)
        {
            if (clefList[0] == "treble")
            {
                SetFirstStaveClef(FM_ClefValue.TREBLE);
                firstStaveClef = FM_ClefValue.TREBLE;
            }
            else
            {
                SetFirstStaveClef(FM_ClefValue.BASS);
                firstStaveClef = FM_ClefValue.BASS;
            }
        }
        if (clefList.Count > 1)
        {
            if (clefList[1] == "treble")
            {
                SetSecondStaveClef(FM_ClefValue.TREBLE);
                secondStaveClef = FM_ClefValue.TREBLE;
            }
            else
            {
                SetSecondStaveClef(FM_ClefValue.BASS);
                secondStaveClef = FM_ClefValue.BASS;
            }
        }

        var originalFirstStaveClef = firstStaveClef;
        var originalSecondStaveClef = secondStaveClef;

        int i = 0;
        string beam = "";
        string tie;
        string tuple = "";
        Dictionary<int, List<FM_BaseNote>> Notes = new Dictionary<int, List<FM_BaseNote>>();
        Dictionary<int, List<int>> Staves = new Dictionary<int, List<int>>();

        while (i < keyList.Count)
        {
            //GameDebug.Log(keyList[i]);
            FM_Key key = new FM_Key(keyList[i]);// Newtonsoft.Json.JsonConvert.DeserializeObject<FM_Key>(keyList[i]);

            if (key.type == FM_KeyType.Bar)
            {
                Notes = Notes.OrderBy(o => o.Key).ToDictionary(o => o.Key, p => p.Value);
                foreach (var k in Notes.Keys)
                    AddChord(Notes[k], Staves[k]);

                Notes.Clear();
                Staves.Clear();
                AddStaveNote(new FM_BarNote(this));
                i++;
                continue;
            }

            if (key.type == FM_KeyType.Clef)
            {
                firstStaveClef = key.clef;
                FM_BaseNote c;
                if (firstStaveClef == FM_ClefValue.BASS)
                    c = new FM_Clef(this, FM_ClefValue.BASS, 0);
                else
                    c = new FM_Clef(this, FM_ClefValue.TREBLE, 0);

                List<FM_BaseNote> noteList = new List<FM_BaseNote>();
                List<int> staveList = new List<int>();
                noteList.Add(c);
                staveList.Add(0);
                Notes[key.chord] = noteList;
                Staves[key.chord] = staveList;
                i++;
                continue;
            }

            if (!string.IsNullOrEmpty(beam) && (string.IsNullOrEmpty(key.beam) || key.beam != beam))
            {
                EndBeam();
                //Debug.LogError($"EndBeam:{i}");
            }

            if (!string.IsNullOrEmpty(tuple) && (string.IsNullOrEmpty(key.tuple) || key.tuple != tuple))
            {
                EndTuplet();
                //Debug.LogError($"EndTuplet:{i}");
            }

            if (!key.beam.Equals(beam) && !string.IsNullOrEmpty(key.beam))
            {
                BeginBeam();
                //Debug.LogError($"BeginBeam:{i}");
            }

            if (!key.tuple.Equals(tuple) && !string.IsNullOrEmpty(key.tuple))
            {
                BeginTuplet(key.tuple);
                //Debug.LogError($"BeginTuplet:{i}");
            }

            beam = key.beam;
            tie = key.tie;
            tuple = key.tuple;

            FM_BaseNote n;
            if (key.note == FM_NoteValue.REST)
            {
                n = new FM_Pause(this, key.duration, key.octave, key.voice);
            }
            else
            {
                var clef = firstStaveClef;
                if (key.stave == 1)
                    clef = secondStaveClef;

                n = new FM_Note(this, key.note, key.octave, clef, key.accidental, key.duration, key.voice, key.stemUp);
                n.stem = key.stem;

                if (!string.IsNullOrEmpty(beam))
                    AddToBeam((FM_Note)n);

                if (!string.IsNullOrEmpty(tie))
                    AddToTie(tie, (FM_Note)n);
            }

            if (!string.IsNullOrEmpty(tuple))
                AddToTuplet(n);

            List<FM_BaseNote> noteList2 = Notes.ContainsKey(key.chord) ? Notes[key.chord] : new List<FM_BaseNote>();
            List<int> staveList2 = Staves.ContainsKey(key.chord) ? Staves[key.chord] : new List<int>();

            noteList2.Add(n);
            staveList2.Add(key.stave);

            Notes[key.chord] = noteList2;
            Staves[key.chord] = staveList2;
            i++;
        }

        foreach (var k in Notes.Keys)
            AddChord(Notes[k], Staves[k]);

        Notes.Clear();
        Staves.Clear();

        if (!string.IsNullOrEmpty(beam))
            EndBeam();

        if (!string.IsNullOrEmpty(tuple))
            EndTuplet();

        SetFirstStaveClef(originalFirstStaveClef);
        SetSecondStaveClef(originalSecondStaveClef);

        //Debug.LogError($" Beams:{Beams.Count}");
        return 0;
    }

    public void ClearNotes()
    {
        this.StaveNotes.Clear();
        this.BeamNotes.Clear();
        this.TieNotes.Clear();
        Ties.Clear();
        Tuplets.Clear();
        Beams.Clear();
        TupletNotes.Clear();
        inTuplet = false;
        TupletPosition = 0;
        TupletSize = 0;
        currentTuplet = 0;
        inBeam = false;
        currentBeam = 0;
    }
}